<?php


class TishHighlighter {
	protected $functionReplaceCallback;
	public $languages = array(
		'php'=>array(
			'type'=>'server_side',
			'scriptLimiters'=>array(
				':(\<\?php\s):'=>':(\?\>):',
				':(\<\?=):'=>':(\?\>):',
				':(\<\?\s+):'=>':(\?\>):'
				//':(\<\?php)(\n|.)+(\?\>):',
				//':(\<\?=)(\n|.)+(\?\>):'
			),
			'comments'=>array(
				'singleLine'=>array(
					':(\s)(#)(.)+:',
					':(\s)(//)(.)+:'
				),
				'multiLine'=>array(
					':(/\*):'=>':(\*/):'
				)
			),
			'includeLimiters'=>TRUE,
			'digits'=>array(
				'highlight'=>TRUE,
				'regex'=>':([\(-+=\*/\)\[\]\{\}\s;\>\<,])?([0-9]+)([0-9\.]+)?([\(-+=\*/\)\[\]\{\}\s;\>\<,]):',
				'matchedIndex'=>array(2, 3)
			),
			'scriptTags'=>array(
				'\<\?php',
				'\<\?=',
				'\?\>',
				'\<\?'
			),
					
			'functions'=>array(
				'regex'=>':(@)?([A-Za-z0-9_]+)(\s?+)(\():',
				'matchedIndex'=>array(1,2)
			),
			
			
			'variables'=>array(
				'regex'=>':(\$)([A-Za-z0-9_]+):'
			),
			'keywords'=>array(
				'language-constructs-separate'=>array(
					'keywords'=>array(
						'echo',
						'as',
						'public',
						'static',
						'protected',
						'var',
						'function',
						'class',
						'abstract',
						'use',
						'namespace',
						'global',
						'new',
						'extends',
						'interface',
						'implements',
						'or',
						'throw'
					),
					'preRegex'=>':([\s\n]+)?',
					'postRegex'=>'([\s\n]+):'
					
				),
				'language-constructs-normal'=>array(
					'keywords'=>array(
						'array',
						'null',
						'elseif',
						'endif',		
						'if',
						'else',
						'endwhile',
						'endforeach',
						'endfor',
						'while',
						'foreach',
						'for',
						'include_once',
						'include',
						'require_once',
						'require',
						'continue',
						'switch',
						'break',
						'default',
						'case',
						'return',
						'TRUE',
						'FALSE',
						'true', 
						'false',
						'list', 
						'parent',
						'string',
						'self'
					),
					'preRegex'=>'+([\(\)\{\}\[\]\s;,:])?',
					'postRegex'=>'([\(\)\{\}\[\]\s;,:])?+'
					
				),
				'variable-casting'=>array(
					'keywords'=>array(
						'int',
						'bool',
						'integer',
						'boolean',
						'float',
						'double',
						'real',
						'string',
						'array',
						'object',
						'unset',
						'binary'
					),
					'preRegex'=>'*(\()(\s)?+',
					'postRegex'=>'(\s)?+(\))*',
					'matchIndex'=>3
				)
			),
			'stringLimiters'=>array(
				'variables'=>array(
					':":'=>':(([^\\\])?(")|(\\\\)?(")):'
				),
				'noVariables'=>array(
					":':"=>":(([^\\\])?(')?(')|(\\\\)?(')?(')):"
				)
			)
		),
		'style'=>array(
			'type'=>'client_side',
			'scriptLimiters'=>array(
				':(\<style\s)([^\>]+)(\>):'=>':(\<\/style\>):'
			),
			'scriptTags'=>array(
				'\<style',
				'\>',
				'/\>'
			),
			'digits'=>array(
				'highlight'=>TRUE,
				'regex'=>':([\(-+=\*/\)\[\]\{\}\s;\>\<])?([0-9]+)([0-9\.]+)?(\s?+(px|em|%))?([\(-+=\*/\)\[\]\{\}\s;\>\<]):',
				'matchedIndex'=>array(2, 3, 4),
				'unincludedArrayParts'=>array(5)
			),
			'includeLimiters'=>FALSE,
			'functions'=>array(
				'regex'=>':([A-Za-z0-9_#\.-]+)(\s?+)(\{):',
				'matchedIndex'=>array(1)
			),
			'comments'=>array(
				'singleLine'=>array(),
				'multiLine'=>array(
					':(/\*):'=>':(\*/):'
				)
			),
			'additionalRegex'=>array(
				'colors'=>':(#)([A-Fa-f0-9]{3})(([A-Fa-f0-9]){3})?:'
			),
			'keywords'=>array(
				'values'=>array(
					'keywords'=>array(
						// Generic
						'both',
						'inherit',
						'auto',
						'none',
						'hidden',
						// Images
						'url',
						'repeat-x',
						'repeat-y',
						'no-repeat',
						
						
						
						// Positioning
						'top',
						'bottom',
						'left',
						'right',
						'center',
						'justify',
						'behind',
						'center-right',
						'center-left',
						'far-left',
						'far-right',
						'left-side',
						'leftwards',
						'right-side',
						'rightwards',
						
						
						'collapse',
						'separate',
						
						// Display
						'block',
						'compact',
						'inline',
						'inline-block',
						'inline-table',
						'list-item',
						'marker',
						'run-in',
						'table',
						'table-caption',
						'table-cell',
						'table-column',
						'table-column-group',
						'table-footer-group',
						'table-header-group',
						'table-row',
						'table-row-group',
						
						// Borders
						'dashed',
						'solid',
						'dotted',
						
						// Fonts
						'sans-serif',
						'monospace',
						'serif',
						
						// Color keywords
						'aqua',
						'black',
						'blue',
						'fuchsia',
						'grey', 
						'green',
						'lime',
						'maroon',
						'navy', 
						'olive',
						'purple',
						'red',
						'rgb',
						'silver',
						'teal',
						'white',
						'yellow',
						
						// List Style Types
						'armenian',
						'circle',
						'cjk-ideographic',
						'decimal',
						'decimal-leading-zero',
						'disc',
						'georgian',
						'hebrew',
						'hiragana',
						'hiragana-iroha',
						'katakana',
						'katakana-iroha',
						'lower-alpha',
						'lower-greek',
						'lower-latin',
						'lower-roman',
						'square',
						'upper-alpha',
						'upper-latin',
						'upper-roman'
					),
					'preRegex'=>'*(:)([^;]+)?([\(\)\{\}\[\]\s,:])?',
					'postRegex'=>'([\(\)\{\}\[\]\s;,:])?*',
					'matchIndex'=>4
					
				),
				'properties'=>array(
					'keywords'=>array(				
						'azimuth',
						
						'background-attachment',
						'background-color',
						'background-image',
						'background-position',
						'background-repeat',
						'background',
						'border-collapse',
						'border-color',
						'border-spacing',
						'border-style',
						'border-top',
						'border-right',
						'border-bottom',
						'border-left',
						'border-top-color',
						'border-right-color',
						'border-bottom-color',
						'border-left-color',
						'border-top-style',
						'border-right-style',
						'border-bottom-style',
						'border-left-style',
						'border-top-width',
						'border-right-width',
						'border-bottom-width',
						'border-left-width',
						'border-width',
						'border',
						
						'caption-side',
						'clear',
						'clip',
						'color',
						'content',
						'counter-increment',
						'counter-reset',
						
						'cue-after',
						'cue-before',
						'cue',
						'cursor',
						'direction',
						'display',
						'elevation',
						'empty-cells',
						'float',
						
						'font-family',
						'font-size',
						'font-size-adjust',
						'font-stretch',
						'font-style',
						'font-variant',
						'font-weight',
						'font',
						
						'letter-spacing',
						'line-height',
						'list-style',
						'list-style-image',
						'list-style-position',
						'list-style-type',
						
						'margin-top',
						'margin-right',
						'margin-bottom',
						'margin-left',
						'marker-offset',
						'margin',
						'marks',
						'max-height',
						'max-width',
						'min-height',
						'min-width',
						'orphans',
						
						'outline-color',
						'outline-style',
						'outline-width',
						'outline',
						
						'overflow-x',
						'overflow-y',
						'overflow',
						
						'padding-top',
						'padding-right',
						'padding-bottom',
						'padding-left',
						'padding',
						
						'page-break-after',
						'page-break-before',
						'page-break-inside',
						'page',
						
						'pause-after',
						'pause-before',
						'pause',
						
						'pitch-range',
						'play-during',
						'pitch',
						'position',
						'quotes',
						'richness',
						
						'size',
						
						'speak-header',
						'speak-numeral',
						'speak-punctuation',
						'speak',
						'speech-rate',
						
						'stress',
						'table-layout',
						'text-align',
						'text-decoration',
						'text-indent',
						'text-shadow',
						'text-transform',
						'unicode-bidi',
						'vertical-align',
						'visibility',
						'voice-family',
						'volume',
						'white-space',
						'widows',
						'word-spacing',
						'z-index',
						'right',
						'top',
						'bottom',
						'left',
						'height',
						'width'
					),
					'preRegex'=>'+([\(\)\{\}\[\]\s;,:])?',
					'postRegex'=>'([\(\)\{\}\[\]\s;,:])?+'
				)
			)
		),
		'script'=>array(
			'type'=>'client_side',
			'scriptLimiters'=>array(
				':(\<script\s)([^\>]+)(\>):'=>':(\<\/script\>):'
			),
			'scriptTags'=>array(
				'\<script',
				'\>',
				'/\>'
			),
			'digits'=>array(
				'highlight'=>TRUE,
				'regex'=>':([\(-+=\*/\)\[\]\{\}\s;\>\<])?([0-9]+)([0-9\.]+)?([\(-+=\*/\)\[\]\{\}\s;\>\<]):',
				'matchedIndex'=>array(2, 3)
			),
			'includeLimiters'=>FALSE,
			'functions'=>array(
				'regex'=>':([A-Za-z0-9_]+)(\s?+)(\():',
				'matchedIndex'=>array(1)
			),
			
			'keywords'=>array(
				'declarations'=>array(
					'keywords'=>array(
						'new',
						'var',
						'function'
					),
					'preRegex'=>':([\s\n]+)?',
					'postRegex'=>'([\s\n]+):'
					
				),
				'language-constructs'=>array(
					'keywords'=>array(
						'this',
						'if',
						'else',
						'while',
						'for',
						'switch',
						'break',
						'default',
						'case',
						'return',
						'true', 
						'false'
					),
					'preRegex'=>'@([\(\)\{\}\[\]\s;,:\n])?',
					'postRegex'=>'([\(\)\{\}\[\]\s;,:\n])?@'
					
				)
			),
			'stringLimiters'=>array(
				'noVariables'=>array(
					":':"=>":([^\\\])?('):",
					':":'=>':([^\\\])?("):'
				)
			),
			'additionalRegex'=>array(
				'regex'=>':(\/)([^\/]+)(\/)(g)?:'
			)
			
		),
		'html'=>array(
			'type'=>'client_side',
			'scriptLimiters'=>array(
				':(\</?)(abbr|acronym|address|area|base|bdo|big|caption|cite|colgroup|col|del|dfn|DOCTYPE|ins|kbd|map|noscript|object|optgroup|param|var|small|sub|sup|tt|table|thead|tfoot|tbody|tr|td|th|style|blockquote|legend|fieldset|label|form|select|input|button|title|textarea|embed|script|meta|link|option|strong|embed|html|body|head|h1|h2|h3|h4|h5|h6|br|hr|p|b|i|u|a|pre|code|span|div|img|ul|li|ol|dl|dd|dt|em):'=>':(/?\>):'
			),
			'includeLimiters'=>TRUE,
			'scriptTags'=>array(
				'\<abbr',
				'\</abbr',
				'\<acronym',
				'\</acronym',
				'\<address',
				'\</address',
				'\<area',
				'\</area',
				'\<base',
				'\</base',
				'\<blockquote',
				'\</blockquote',
				'\<bdo',
				'\</bdo',
				'\<big',
				'\</big',
				'\<caption',
				'\</caption',
				'\<cite',
				'\</cite',
				'\<colgroup',
				'\</colgroup',
				'\<col',
				'\</col',
				'\<del',
				'\</del',
				'\<dfn',
				'\</dfn',
				'\<DOCTYPE',
				'\</DOCTYPE',
				'\<ins',
				'\</ins',
				'\<kbd',
				'\</kbd',
				'\<map',
				'\</map',
				'\<noscript',
				'\</noscript',
				'\<object',
				'\</object',
				'\<optgroup',
				'\</optgroup',
				'\<param',
				'\</param',
				'\<small',
				'\</small',
				'\<sub',
				'\</sub',
				'\<sup',
				'\</sup',
				'\<style',
				'\</style',
				'\<pre',
				'\</pre',
				'\<code',
				'\</code',
				'\<span',
				'\</span',
				'\<table',
				'\</table',
				'\<thead',
				'\</thead',
				'\<tfoot',
				'\</tfoot',
				'\<tbody',
				'\</tbody',
				'\<tr',
				'\</tr',
				'\<td',
				'\</td',
				'\<th',
				'\</th',
				'\<div',
				'\</div',
				'\<title',
				'\</title',
				'\<fieldset',
				'\</fieldset',
				'\<legend',
				'\</legend',
				'\<label',
				'\</label',
				'\<form',
				'\</form',
				'\<select',
				'\</select',
				'\<input',
				'\</input',
				'\<option',
				'\</option',
				'\<button',
				'\</button',
				'\<textarea',
				'\</textarea',
				'\<script',
				'\</script',
				'\<meta',
				'\</meta',
				'\<link',
				'\</link',
				'\<strong',
				'\</strong',
				'\<embed',
				'\</embed',
				'\<html',
				'\</html',
				'\<body',
				'\</body',
				'\<head',
				'\</head',
				'\<img',
				'\</img',
				'\<var',
				'\</var',
				'\<ul',
				'\</ul',
				'\<li',
				'\</li',
				'\<ol',
				'\</ol',
				'\<br',
				'\</br',
				'\<hr',
				'\</hr',
				'\<dl',
				'\</dl',
				'\<dd',
				'\</dd',
				'\<dt',
				'\</dt',			
				'\<em',
				'\</em',			
				'\<h1',
				'\</h1',
				'\<h2',
				'\</h2',
				'\<h3',
				'\</h3',
				'\<h4',
				'\</h4',
				'\<h5',
				'\</h5',
				'\<h6',
				'\</h6',
				'\<p',
				'\</p',
				'\<b',
				'\</b',
				'\<i',
				'\</i',
				'\<u',
				'\</u',
				'\<a',
				'\</a',
				'\<tt',
				'\</tt',
				'\>',
				'/\>'
			),
			'stringLimiters'=>array(
				'noVariables'=>array(
					":':"=>":([^\\\])?('):",
					':":'=>':([^\\\])?("):'
				)
			)
			
		)
					
					
	);
	
	public function setFunctionReplaceCallback($function) {
		$this->functionReplaceCallback = $function;
	}
	
	
	public static function getBlocksBetween($open, $close, $code, $includeTags = FALSE) {
		$closerPos = 0;
		preg_match_all($open, $code, $openers);
		$blocks = array();
		foreach($openers[0] as $opener) {
			if($includeTags) {
				$position = strpos($code, $opener);
				

				preg_match($close, substr($code, $position + 1), $closers);
		
		
				$closerPos = strpos($code, $closers[0], $position + 1) + strlen($closers[0]);
				if($closerPos) {
					$match = substr($code, $position, ($closerPos - $position));
					$blocks[] = $match;
				}
				
				
				
			}
			else {
				
				 
				$position = strpos($code, $opener) + strlen($opener);
				
				
				
								
				
				preg_match($close, substr($code, $position), $closers);
				$closerPos = strpos($code, $closers[0], $position);
				if($closerPos) {
					$match = substr($code, $position, ($closerPos - $position));
					$blocks[] = $match;
				}
			}
			
			$code = substr($code, $closerPos);
			
		}
		return $blocks;
	}
	
	public static function strReplaceOnce($needle, $replace, $haystack) {
		if(!$needle || !$haystack) {
			return $haystack;
		}
		$pos = strpos($haystack, $needle);
		if($pos === FALSE) {
			return $haystack;
		}
		return substr_replace($haystack, $replace, $pos, strlen($needle));
	}
	
	public static function strPosAll($haystack,$needle, $pos = 'start') {
	   
	    $s=0;
	    $i=0;
	   
	    while (is_integer($i)){
	       
	        $i = strpos($haystack,$needle,$s);
	       	if($pos == 'end') {
	       		$n = $i + strlen($needle);
	       	}
	       	else {
	       		$n = $i;
	       	}
	        if (is_integer($n)) {
	            $aStrPos[] = $n;
	            $s = $i+strlen($needle);
	        }
	    }
	    if (isset($aStrPos)) {
	    	return $aStrPos;
	        
	    }
	    else {
	        return false;
	    }
	}
	
	protected static function displayCode($code) {
		echo $code;
	}
	
	protected function parsePureScript($code) {	
		$blocks = array();
		$id = uniqid('sh_block_1').'_^id^';
		
		$blocks['script'][$id] = $code;
		$parsedBlocks = $this->parseBlocks($blocks);
		$code = $this->insertParsedBlocks($id, $parsedBlocks);
		
		self::displayCode($code);
	}
	
	protected function parsePureStyle($code) {	
		$blocks = array();
		$id = uniqid('sh_block_1').'_^id^';
		$blocks['style'][$id] = $code;
		$parsedBlocks = $this->parseBlocks($blocks);
		$code = $this->insertParsedBlocks($id, $parsedBlocks);
		
		self::displayCode($code);
	}
	
	protected function parsePurePHP($code) {	
		$blocks = array();
		$id = uniqid('sh_block_1').'_^id^';
		$blocks['php'][$id] = $code;
		$parsedBlocks = $this->parseBlocks($blocks);
		$code = $this->insertParsedBlocks($id, $parsedBlocks);
		
		self::displayCode($code);
	}
	
	protected function parsePureHTML($code) {	
		$blocks = array();
		$id = uniqid('sh_block_1').'_^id^';
		$blocks['html'][$id] = $code;
		$parsedBlocks = $this->parseBlocks($blocks);
		$code = $this->insertParsedBlocks($id, $parsedBlocks);
		
		self::displayCode($code);
	}
	
	protected function parseMixedHtml($code) {
		$blocks = array();
		$i = 1;
		// Get script blocks and php blocks
		foreach($this->languages as $languageName=>$language) {
			// Get blocks
			foreach($language['scriptLimiters'] as $open=>$close) {
				$between = self::getBlocksBetween($open, $close, $code, $language['includeLimiters']);
				foreach($between as $block) {
					$id = uniqid('sh_block_'.$i).'_^id^';
					$blocks[$languageName][$id] = $block;
					$code = self::strReplaceOnce($block, $id, $code);
					$i++;
					
				}
			}
		}
		
		// Divided into blocks, now let's highlight it
		$parsedBlocks = $this->parseBlocks($blocks);
		
		$code = $this->insertParsedBlocks($code, $parsedBlocks);
		
		
		self::displayCode($code);
		


		
	}
	
	protected function insertParsedBlocks($code, $parsedBlocks) {
		if(count($parsedBlocks['client_side'])) {
			foreach($parsedBlocks['client_side'] as $replace=>$with) {
				$code = str_replace($replace, $with, $code);
			}
		}
		
		
		if(count($parsedBlocks['server_side'])) {
			foreach($parsedBlocks['server_side'] as $replace=>$with) {
				$code = str_replace($replace, $with, $code);
			}
		}
		return $code;
	}	
	
	protected function parseBlocks($blocks) {
		global $parsedBlocks;
		global $replacements;
		global $currentLanguage;
		global $i;
		global $type;
		global $keywords;
		global $functionReplaceCallback;
		$functionReplaceCallback = $this->functionReplaceCallback;
		$i = 1;
		$parsedBlocks = array();
		foreach($blocks as $language=>$languageBlocks) {
			$currentLanguage = $this->languages[$language];
			foreach($languageBlocks as $index=>$block) {
				$replacements = array();
				//echo '<h4>Parsing Block #'.$i.' ('.$language.')</h4>';
				// Highlight, replace into code
				
				// Comments
				if($comments = $this->languages[$language]['comments']) {
					foreach($comments['multiLine'] as $open=>$close) {
						$betweens = self::getBlocksBetween($open, $close, $block, TRUE);
						foreach($betweens as $between) {
							$id = uniqid('sh_'.$i).'_^id^';
							$replacements[12][$id] = '<span class="comment">'.htmlentities($between).'</span>';
							$block = self::strReplaceOnce($between, $id, $block);
							$i++;
						}	
					}
					foreach($comments['singleLine'] as $regex) {
						$block = preg_replace_callback($regex, 
							function($matches) {
								global $replacements;
								global $i;
								$id = uniqid('sh_'.$i).'_^id^';
								$replacements[11][$id] = '<span class="comment">'.htmlentities($matches[0]).'</span>';
								$i++;
								return $id;
								
							}, $block);
					}
				}
				// Script limiters
				$block = preg_replace_callback(':('.implode('|', $this->languages[$language]['scriptTags']).'):',
						function($matches) {
							global $replacements;
							global $i;
							$id = uniqid('sh_'.$i).'_^id^';
							$replacements[10][$id] = '<span class="script-limiter">'.htmlentities($matches[0]).'</span>';
							$i++;
							return $id;
						}, $block);
				
				
				
				// Strings
				$variables = $this->languages[$language]['stringLimiters']['variables'];
				$noVariables = $this->languages[$language]['stringLimiters']['noVariables'];
				
				// Variables
				if($regex = $this->languages[$language]['variables']['regex']) {
					$block = preg_replace_callback($regex,
								function($matches) {
									global $replacements;
									global $i;
									$id = uniqid('sh_'.$i).'_^id^';
									$replacements[9][$id] = '<span class="variable">'.htmlentities($matches[0]).'</span>';
									$replacements[5][$id] = '<span class="variable">'.htmlentities($matches[0]).'</span>';
									$replacements[7][$id] = htmlentities($matches[0]);
									$i++;
									return $id;
								}, $block);
				}
				
				if($variables) {
					foreach($variables as $open=>$close) {
						$betweens = self::getBlocksBetween($open, $close, $block, TRUE);
						foreach($betweens as $between) {
							$id = uniqid('sh_'.$i).'_^id^';
							$replacements[8][$id] = '<span class="string">'.htmlentities($between).'</span>';
							$block = self::strReplaceOnce($between, $id, $block);
							$i++;
							
						}	
					}
					
				}
				
				if($noVariables) {
					foreach($noVariables as $open=>$close) {
						$betweens = self::getBlocksBetween($open, $close, $block, TRUE);
						foreach($betweens as $between) {
							$id = uniqid('sh_'.$i).'_^id^';
							$i++;
							$replacements[6][$id] = '<span class="string">'.htmlentities($between).'</span>';
							$block = self::strReplaceOnce($between, $id, $block);
							
						}	
					}
					
				}

				
				
				
			
				
				
				// Keywords
				
				
				if(count($this->languages[$language]['keywords'])) {
					foreach($this->languages[$language]['keywords'] as $type=>$keywords) {
						$regex = $keywords['preRegex'].'('.implode('|', $keywords['keywords']).')'.$keywords['postRegex'];
						$block = preg_replace_callback($regex,
							function($matches) {
								global $replacements;
								global $i;
								global $type;
								global $keywords;
								$id = uniqid('sh_'. $i).'_^id^';
								if(!$keywords['matchIndex']) $matchIndex = 2;
								else $matchIndex = $keywords['matchIndex'];
								for($i=1; $i<=count($matches); $i++) {
									if($i == $matchIndex) $match = $matches[$i];
									elseif($i < $matchIndex) $before .= $matches[$i];
									elseif($i > $matchIndex) $after .= $matches[$i];
								}
								$replacements[5][$id] = '<span class="keywords-'.$type.'">'.htmlentities($match).'</span>';
								$i++;
								return $before.$id.$after;
							}, $block);
					}
				}
				
				
				
				// Functions
				
				if($this->languages[$language]['functions']['regex']) {
					$block = preg_replace_callback($this->languages[$language]['functions']['regex'],
								function($matches) {
									global $replacements;
									global $currentLanguage;
									global $functionReplaceCallback;
									$count = count($matches) - 1;
									global $i;
									
									$id = uniqid('sh_'.$i).'_^id^';
									$i++;
									foreach($currentLanguage['functions']['matchedIndex'] as $part) {
										$highlight .= $matches[$part];
									}
									$where = 'before';
									for($x=1; $x<=$count;$x++) {
										
										if(in_array($x, $currentLanguage['functions']['matchedIndex'])) {
											$where = 'after';
										}
										
										elseif($where == 'before') {
											$before .= $matches[$x];
										}
										elseif($where == 'after') {
											$after .= $matches[$x];
										}
									}
									if($functionReplaceCallback) {
										$highlight = $functionReplaceCallback(htmlentities($highlight));
									}
									else {
										$highlight = '<span class="function">'.htmlentities($highlight).'</span>';
									}
									$replacements[3][$id] = $before.$highlight.$after;
									return $id;
								}, $block);
				}
				
				// Additional regex
				if(count($this->languages[$language]['additionalRegex'])) {
					foreach($this->languages[$language]['additionalRegex'] as $type=>$regex) {
						$block = preg_replace_callback($regex, 
									function($matches) {
										global $replacements;
										global $i;
										global $type;
										$id = uniqid('sh_'.$i).'_^id^';
										$i++;
										$replacements[2][$id] = '<span class="additional-'.$type.'">'.$matches[0].'</span>';
										return $id;
									},
									$block);
					}
				}
				
				// Digits
				if($this->languages[$language]['digits']['highlight']) {
					$block = preg_replace_callback($this->languages[$language]['digits']['regex'], 
									function($matches) {
										global $replacements;
										global $currentLanguage;
										global $i;
										$id = uniqid('sh_'.$i).'_^id^';
										$i++;
										foreach($currentLanguage['digits']['matchedIndex'] as $part) {
											$highlight .= $matches[$part];
										}
										$where = 'before';
										$count = count($matches) - 1;
										for($x=1; $x<=$count;$x++) {
											
											if(in_array($x, $currentLanguage['digits']['matchedIndex'])) {
												$where = 'after';
												continue;
											}
											elseif(is_array($currentLanguage['digits']['unincludedArrayParts']) && in_array($x, $currentLanguage['digits']['unincludedArrayParts'])) {
												continue;
											}
											elseif($where == 'before') {
												$before .= $matches[$x];
											}
											elseif($where == 'after') {
												$after .= $matches[$x];
											}
										}
										$replacements[1][$id] = '<span class="digit">'.htmlentities($highlight).'</span>';
										return $before.$id.$after;
									
									}, $block);
				}
				for($y=1;$y<=12;$y++){
					if(count($replacements[$y])) {
						foreach($replacements[$y] as $replace=>$with) {
							$block = str_replace($replace, $with, $block);
						}
					}
				}
				$block = '<span class="'.$language.'">'.$block.'</span>';
				$parsedBlocks[$this->languages[$language]['type']][$index] = $block;
				
			}
			
		}
		
		return $parsedBlocks;
	}
	
	public function parse($code, $mode = 'MixedHTML') {
		switch(strtolower($mode)) {
			case 'mixedhtml':
			default:
				$this->parseMixedHTML($code);
				break;
			case 'html':
				$this->parsePureHTML($code);
				break;
			case 'script':
			case 'javascript':
			case 'java script':
				$this->parsePureScript($code);
				break;
			case 'php':
				$this->parsePurePHP($code);
				break;
			case 'style':
			case 'css':
				$this->parsePureStyle($code);
				break;
		}
		
		
	}
}